/**
 * 
 */
package slhs.tempo.pivotTableUtilities;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.table.TableModel;

import slhs.tempo.TableAnalyzer.ConcreteTableModelListener;
import slhs.tempo.TableAnalyzer.SaveCurrentState;
import slhs.tempo.TableAnalyzer.TablePanelHandler;
import slhs.tempo.scriptGenerator.UtilityConstants;

/**
 * StatsCalculator.java
 * 
 * @author Swapnil Devikar
 * 
 */
public class StatsCalculator {

    private TablePanelHandler tablePanelHandler;
    private TableModel latestDataModel;
    private String[] subjectList;
    private static SubjectStat[] stats;

    private boolean overallMeanStatsPresent = false;
    private boolean controlMeanStatsPresent = false;
    private boolean relatedMeanStatsPresent = false;
    private boolean overallSDStatsPresent = false;
    private boolean controlSDStatsPresent = false;
    private boolean relatedSDStatsPresent = false;
    private int numberOfSubjects = 0;
    private Set<String> probePointsSet = null;
    HashMap<String, ArrayList<Integer>> perProbePointData = null;

    public StatsCalculator(TablePanelHandler tablePanelHandlerObject) {
	this.tablePanelHandler = tablePanelHandlerObject;
	this.latestDataModel = ConcreteTableModelListener.getUpdatedModel();
	subjectList = tablePanelHandlerObject.getSubjectList();
	stats = new SubjectStat[subjectList.length];

	// for each subject, populate control and related reaction times
	numberOfSubjects = populateReactionTimesAcrossProbes();

	// for each subject, count number of reactions
    }

    /**
     * Populate reaction times for each subject with no consideration to probe point
     */
    private int populateReactionTimesAcrossProbes() {
	int tableRowsCount = latestDataModel.getRowCount();
	int probePointColumnIndex = ConcreteTableModelListener
		.getColumnIndexByName(UtilityConstants.PROBE_POINT_COLUMN_HEADER);
	int reactionTime = -1;
	int subjectIndex = -1;
	String responseType = null;
	String responseProbePoint = null;

	for (int rowIndex = 0; rowIndex < tableRowsCount; rowIndex++) {
	    Object currentSubject = latestDataModel.getValueAt(rowIndex, 0);
	    reactionTime = this.getReactionTime(rowIndex);
	    responseType = this.getReactionType(rowIndex);
	    responseProbePoint = this.getResponseProbePoint(rowIndex, probePointColumnIndex);
	    try {
		// if subject with this id already exist, just append to the reaction times list
		if (stats[subjectIndex].getSubjectId().equals(currentSubject.toString())) {
		    stats[subjectIndex].addToOverallReactionTime(reactionTime);
		    stats[subjectIndex].addToProbePointReactionTimeAcrossAllReactionTimes(reactionTime,
			    responseProbePoint);

		    if (responseType.equalsIgnoreCase(UtilityConstants.CONTROL_TYPE_REACTION)) {
			stats[subjectIndex].addToControlReactionTime(reactionTime);
			stats[subjectIndex].addToProbePointReactionTimeForControlReactionTimes(reactionTime,
				responseProbePoint);

		    } else if (responseType.equalsIgnoreCase(UtilityConstants.RELATED_TYPE_REACTION)) {
			stats[subjectIndex].addToRelatedReactionTime(reactionTime);
			stats[subjectIndex].addToProbePointReactionTimeForRelatedReactionTimes(reactionTime,
				responseProbePoint);

		    }

		} else {
		    subjectIndex++;
		    stats[subjectIndex] = new SubjectStat(currentSubject.toString());
		    stats[subjectIndex].addToOverallReactionTime(reactionTime);
		    stats[subjectIndex].addToProbePointReactionTimeAcrossAllReactionTimes(reactionTime,
			    responseProbePoint);

		    if (responseType.equalsIgnoreCase(UtilityConstants.CONTROL_TYPE_REACTION)) {
			stats[subjectIndex].addToControlReactionTime(reactionTime);
			stats[subjectIndex].addToProbePointReactionTimeForControlReactionTimes(reactionTime,
				responseProbePoint);

		    } else if (responseType.equalsIgnoreCase(UtilityConstants.RELATED_TYPE_REACTION)) {
			stats[subjectIndex].addToRelatedReactionTime(reactionTime);
			stats[subjectIndex].addToProbePointReactionTimeForRelatedReactionTimes(reactionTime,
				responseProbePoint);

		    }

		}

	    } catch (Exception e) {
		// if subject doesn't already exist, create one
		subjectIndex++;
		stats[subjectIndex] = new SubjectStat(currentSubject.toString());
		stats[subjectIndex].addToOverallReactionTime(reactionTime);
		stats[subjectIndex].addToProbePointReactionTimeAcrossAllReactionTimes(reactionTime, responseProbePoint);

		if (responseType.equalsIgnoreCase(UtilityConstants.CONTROL_TYPE_REACTION)) {
		    stats[subjectIndex].addToControlReactionTime(reactionTime);
		    stats[subjectIndex].addToProbePointReactionTimeForControlReactionTimes(reactionTime,
			    responseProbePoint);

		} else if (responseType.equalsIgnoreCase(UtilityConstants.RELATED_TYPE_REACTION)) {
		    stats[subjectIndex].addToRelatedReactionTime(reactionTime);
		    stats[subjectIndex].addToProbePointReactionTimeForRelatedReactionTimes(reactionTime,
			    responseProbePoint);

		}

	    }

	}
	return subjectIndex + 1;
    }

    /**
     * @param rowIndex
     * @return
     */
    private String getResponseProbePoint(int rowIndex, int probePointColumnIndex) {
	String probePoint = (String) latestDataModel.getValueAt(rowIndex, probePointColumnIndex);
	return probePoint;
    }

    /**
     * Pre process the probe point strings and create a set of strings used to represent probe points e.g. the set will
     * contains pp1, pp2 etc. Each subject will thus maintain a hashmap with these probe points as keys and the reaction
     * time against them as values
     * 
     * @return
     */
    protected Set<String> preProcessProbePointStrings() {
	// record the probe points
	probePointsSet = new HashSet<String>();
	int columnCount = latestDataModel.getColumnCount();
	int rowCount = latestDataModel.getRowCount();
	int probePointColumnIndex = -1;
	// search for probe point column
	for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
	    if (latestDataModel.getColumnName(columnIndex).equals(UtilityConstants.PROBE_POINT_COLUMN_HEADER)) {
		probePointColumnIndex = columnIndex;
	    }
	}
	try {
	    for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
		String probePoint = (String) latestDataModel.getValueAt(rowIndex, probePointColumnIndex);
		if (probePoint.equals("") || (probePoint == null)) {
		    // dont add it to set
		} else {
		    probePointsSet.add(probePoint);
		}
	    }
	} catch (Exception e) {
	    System.out.println("Column with title Item Number not found");
	    probePointsSet.clear();
	}
	return probePointsSet;
    }

    /**
     * Populate the Overall mean field in the SubjectStat object for all the subjects in table
     * 
     * @return Array of SubjectStat with Overall Mean field populated
     * */
    public SubjectStat[] generateOverallMeanStats() {

	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateOverallMean();
	}
	overallMeanStatsPresent = true;
	return stats;
    }

    /**
     * Populate the Overall mean field in the SubjectStat object for all the subjects in table
     * 
     * @return Array of SubjectStat with conditional mean fields populated
     * */
    public SubjectStat[] generateConditionalMeanStats() {

	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    if (!controlMeanStatsPresent)
		stats[subjectIndex].calculateControlReactionTimesMean();
	    if (!relatedMeanStatsPresent)
		stats[subjectIndex].calculateRelatedReactionTimesMean();

	}
	controlMeanStatsPresent = true;
	relatedMeanStatsPresent = true;
	return stats;
    }

    /**
     * Populate the Standard deviations for all subjects
     * 
     * @return Array of SubjectStat with Overall Standard Deviation field populated
     * */
    public SubjectStat[] populateStandardDeviations(String analysisType) {
	if (!overallMeanStatsPresent) {
	    generateOverallMeanStats();
	}
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateOverallStandardDeviation();
	}
	return stats;
    }

    /**
     * @param rowIndex
     *            -Index of row for which reaction time is required
     * @return reaction time at rowIndex
     */
    private int getReactionTime(int rowIndex) {
	int reactionTimeColumnIndex = ConcreteTableModelListener
		.getColumnIndexByName(UtilityConstants.REACTION_TIME_COLUMN_IDENTIFIER);
	String reactionTime = latestDataModel.getValueAt(rowIndex, reactionTimeColumnIndex).toString();
	return Integer.valueOf(new Integer(reactionTime));
    }

    /**
     * @param rowIndex
     *            -Index of row for which reaction type is required
     * @return reaction time at rowIndex
     */
    private String getReactionType(int rowIndex) {
	// try searching control with the keyword specified in UtilityConstants
	String columnName = this.getColumnNameByItemName(UtilityConstants.CONTROL_TYPE_REACTION);
	int reactionTypeColumnIndex = ConcreteTableModelListener.getColumnIndexByName(columnName);

	// if not found, try searching with the keyword specified for related
	if (reactionTypeColumnIndex == -1) {
	    columnName = this.getColumnNameByItemName(UtilityConstants.RELATED_TYPE_REACTION);
	    reactionTypeColumnIndex = ConcreteTableModelListener.getColumnIndexByName(columnName);
	}

	// if still not found, as user what keyword is he using for ctl/rel
	if (reactionTypeColumnIndex == -1) {
	    String keyword = JOptionPane
		    .showInputDialog(
			    "Could not find ctl/rel types. You might be using different notations, if so, please specify the exact keyword below",
			    "control");
	    columnName = this.getColumnNameByItemName(keyword);
	    reactionTypeColumnIndex = ConcreteTableModelListener.getColumnIndexByName(columnName);
	}

	String reactionType = latestDataModel.getValueAt(rowIndex, reactionTypeColumnIndex).toString();
	return reactionType;
    }

    /**
     * Method to return the SubjectStat corresponding to subject id
     * 
     * @return SubjectStat object for subjectId
     * */
    public SubjectStat getSubjectStat(String subjectId) {
	int subjectIndex = -1;
	for (int index = 0; index < stats.length; index++) {
	    if (stats[index].getSubjectId().equals(subjectId)) {
		subjectIndex = index;
	    }
	}
	if (subjectIndex != -1)
	    return stats[subjectIndex];
	else
	    return null;
    }

    /**
     * @param searchValue
     *            : String value of cell for which the column name is required
     * @return String value of column header (or simply column name)
     **/
    public String getColumnNameByItemName(String searchValue) {
	int selectedTabIndex = tablePanelHandler.getSelectedTabIndex();
	JTable table = SaveCurrentState.tableNameObjectHashmap.get(new Integer(selectedTabIndex));

	for (int rowIndex = 0; rowIndex < table.getRowCount(); rowIndex++) {
	    for (int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
		try {
		    String currentValue = table.getValueAt(rowIndex, columnIndex).toString();
		    if ((currentValue != null) && (currentValue != "") && (currentValue.equalsIgnoreCase(searchValue))) {
			return table.getColumnName(columnIndex);
		    }
		} catch (NullPointerException e) {
		    // e.printStackTrace();
		}

	    }
	}
	return null;
    }

    /**
     * @return
     */
    public SubjectStat[] generateOverallStandardDeviationStats() {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateOverallStandardDeviation();
	}
	overallSDStatsPresent = true;
	return stats;
    }

    /**
     * @return
     */
    public SubjectStat[] generateRelatedStandardDeviationStats() {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateRelatedStandardDeviation();
	}
	relatedSDStatsPresent = true;
	return stats;
    }

    /**
     * @return
     */
    public SubjectStat[] generateControlStandardDeviationStats() {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateControlStandardDeviation();
	}
	controlSDStatsPresent = true;
	return stats;
    }

    /**
     * @return
     */
    public SubjectStat[] generateOverallZscoreStats() {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateOverallZScore();
	}
	return stats;
    }

    /**
     * @return
     */
    public int getNumberOfSubjects() {
	return numberOfSubjects;
    }

    /**
     * @return
     */
    public SubjectStat[] generateControlZscoreStats() {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateControlZScore();
	}
	return stats;
    }

    /**
     * @return
     */
    public SubjectStat[] generateRelatedZscoreStats() {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    stats[subjectIndex].calculateRelatedZScore();
	}
	return stats;
    }

    /**
     * @param probePointSet
     * @return
     */
    public SubjectStat[] generateMeanValuesForProbePointsAcrossAllReactionTimes(Set<String> probePointSet) {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    for (String currentProbePoint : probePointSet) {
		stats[subjectIndex].calculateMeanAcrossAllReactionTimesForProbe(currentProbePoint);
	    }
	}

	return stats;
    }

    /**
     * @param probePointSet
     * @return
     */
    public SubjectStat[] generateMeanValuesForProbePointsAcrossControlReactionTimes(Set<String> probePointSet) {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    for (String currentProbePoint : probePointSet) {
		stats[subjectIndex].calculateMeanAcrossControlReactionTimesForProbe(currentProbePoint);
	    }
	}
	return stats;
    }

    /**
     * @param probePointSet
     * @return
     */
    public SubjectStat[] generateMeanValuesForProbePointsAcrossRelatedReactionTimes(Set<String> probePointSet) {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    for (String currentProbePoint : probePointSet) {
		stats[subjectIndex].calculateMeanAcrossRelatedReactionTimesForProbe(currentProbePoint);
	    }
	}
	return stats;
    }

    /**
     * @param probePointSet
     * @return
     */
    public SubjectStat[] generateStdDevValuesForProbePointsAcrossAllReactionTimes(Set<String> probePointSet) {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    for (String currentProbePoint : probePointSet) {
		stats[subjectIndex].calculateStdDevAcrossAllReactionTimesForProbe(currentProbePoint);
	    }
	}
	return stats;
    }

    /**
     * @param probePointSet
     * @return
     */
    public SubjectStat[] generateStdDevValuesArrayForProbePointsForRelatedReactionTimes(Set<String> probePointSet) {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    for (String currentProbePoint : probePointSet) {
		stats[subjectIndex].calculateStdDevForRelatedReactionTimesForProbe(currentProbePoint);
	    }
	}
	return stats;
    }

    /**
     * @param probePointSet
     * @return
     */
    public SubjectStat[] generateStdDevValuesArrayForProbePointsForControlReactionTimes(Set<String> probePointSet) {
	for (int subjectIndex = 0; subjectIndex < subjectList.length; subjectIndex++) {
	    for (String currentProbePoint : probePointSet) {
		stats[subjectIndex].calculateStdDevForControlReactionTimesForProbe(currentProbePoint);
	    }
	}
	return stats;
    }
}
